/*
 * Copyright 2014 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */
#include "src/utils/win/SkDWriteNTDDI_VERSION.h"

#include "include/core/SkTypes.h"
#if defined(SK_BUILD_FOR_WIN)

#include "include/core/SkStream.h"
#include "include/core/SkString.h"
#include "include/core/SkTypes.h"
#include "include/ports/SkRemotableFontMgr.h"
#include "include/private/base/SkMutex.h"
#include "include/private/base/SkTArray.h"
#include "src/base/SkUTF.h"
#include "src/ports/SkTypeface_win_dw.h"
#include "src/utils/win/SkDWrite.h"
#include "src/utils/win/SkDWriteFontFileStream.h"
#include "src/utils/win/SkHRESULT.h"
#include "src/utils/win/SkObjBase.h"
#include "src/utils/win/SkTScopedComPtr.h"

#include <dwrite.h>

using namespace skia_private;

class SK_API SkRemotableFontMgr_DirectWrite : public SkRemotableFontMgr {
private:
    struct DataId {
        IUnknown *fLoader; // In COM only IUnknown pointers may be safely used for identity.
        void *fKey;
        UINT32 fKeySize;

        DataId() {}

        DataId(DataId &&that) : fLoader(that.fLoader), fKey(that.fKey), fKeySize(that.fKeySize)
        {
            that.fLoader = nullptr;
            that.fKey = nullptr;
            SkDEBUGCODE(that.fKeySize = 0xFFFFFFFF;)
        }

        ~DataId()
        {
            if (fLoader) {
                fLoader->Release();
            }
            sk_free(fKey);
        }
    };

    mutable TArray<DataId> fDataIdCache;
    mutable SkMutex fDataIdCacheMutex;

    int FindOrAdd(IDWriteFontFileLoader *fontFileLoader, const void *refKey, UINT32 refKeySize) const
    {
        SkTScopedComPtr<IUnknown> fontFileLoaderId;
        HR_GENERAL(fontFileLoader->QueryInterface(&fontFileLoaderId), "Failed to re-convert to IDWriteFontFileLoader.",
            SkFontIdentity::kInvalidDataId);

        SkAutoMutexExclusive ama(fDataIdCacheMutex);
        int count = fDataIdCache.size();
        int i;
        for (i = 0; i < count; ++i) {
            const DataId &current = fDataIdCache[i];
            if (fontFileLoaderId.get() == current.fLoader && refKeySize == current.fKeySize &&
                0 == memcmp(refKey, current.fKey, refKeySize)) {
                return i;
            }
        }
        DataId &added = fDataIdCache.push_back();
        added.fLoader = fontFileLoaderId.release(); // Ref is passed.
        added.fKey = sk_malloc_throw(refKeySize);
        memcpy(added.fKey, refKey, refKeySize);
        added.fKeySize = refKeySize;

        return i;
    }

public:
    /* * localeNameLength must include the null terminator. */
    SkRemotableFontMgr_DirectWrite(IDWriteFontCollection *fontCollection, WCHAR *localeName, int localeNameLength)
        : fFontCollection(SkRefComPtr(fontCollection)), fLocaleName(localeNameLength)
    {
        memcpy(fLocaleName.get(), localeName, localeNameLength * sizeof(WCHAR));
    }

    HRESULT FontToIdentity(IDWriteFont *font, SkFontIdentity *fontId) const
    {
        SkTScopedComPtr<IDWriteFontFace> fontFace;
        HRM(font->CreateFontFace(&fontFace), "Could not create font face.");

        UINT32 numFiles;
        HR(fontFace->GetFiles(&numFiles, nullptr));
        if (numFiles > 1) {
            return E_FAIL;
        }

        // data id
        SkTScopedComPtr<IDWriteFontFile> fontFile;
        HR(fontFace->GetFiles(&numFiles, &fontFile));

        SkTScopedComPtr<IDWriteFontFileLoader> fontFileLoader;
        HR(fontFile->GetLoader(&fontFileLoader));

        const void *refKey;
        UINT32 refKeySize;
        HR(fontFile->GetReferenceKey(&refKey, &refKeySize));

        fontId->fDataId = FindOrAdd(fontFileLoader.get(), refKey, refKeySize);

        // index
        fontId->fTtcIndex = fontFace->GetIndex();

        // style
        fontId->fFontStyle = get_style(font);
        return S_OK;
    }

    SkRemotableFontIdentitySet *getIndex(int familyIndex) const override
    {
        SkTScopedComPtr<IDWriteFontFamily> fontFamily;
        HRNM(fFontCollection->GetFontFamily(familyIndex, &fontFamily), "Could not get requested family.");

        int count = fontFamily->GetFontCount();
        SkFontIdentity *fontIds;
        sk_sp<SkRemotableFontIdentitySet> fontIdSet(new SkRemotableFontIdentitySet(count, &fontIds));
        for (int fontIndex = 0; fontIndex < count; ++fontIndex) {
            SkTScopedComPtr<IDWriteFont> font;
            HRNM(fontFamily->GetFont(fontIndex, &font), "Could not get font.");

            HRN(FontToIdentity(font.get(), &fontIds[fontIndex]));
        }
        return fontIdSet.release();
    }

    virtual SkFontIdentity matchIndexStyle(int familyIndex, const SkFontStyle &pattern) const override
    {
        SkFontIdentity identity = { SkFontIdentity::kInvalidDataId };

        SkTScopedComPtr<IDWriteFontFamily> fontFamily;
        HR_GENERAL(fFontCollection->GetFontFamily(familyIndex, &fontFamily), "Could not get requested family.",
            identity);

        const DWriteStyle dwStyle(pattern);
        SkTScopedComPtr<IDWriteFont> font;
        HR_GENERAL(fontFamily->GetFirstMatchingFont(dwStyle.fWeight, dwStyle.fWidth, dwStyle.fSlant, &font),
            "Could not match font in family.", identity);

        HR_GENERAL(FontToIdentity(font.get(), &identity), nullptr, identity);

        return identity;
    }

    static HRESULT getDefaultFontFamilyName(SkSMallocWCHAR *name)
    {
        NONCLIENTMETRICSW metrics;
        metrics.cbSize = sizeof(metrics);
        if (0 == SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, sizeof(metrics), &metrics, 0)) {
            return E_UNEXPECTED;
        }

        size_t len = wcsnlen_s(metrics.lfMessageFont.lfFaceName, LF_FACESIZE) + 1;
        if (0 != wcsncpy_s(name->reset(len), len, metrics.lfMessageFont.lfFaceName, _TRUNCATE)) {
            return E_UNEXPECTED;
        }

        return S_OK;
    }

    SkRemotableFontIdentitySet *matchName(const char familyName[]) const override
    {
        SkSMallocWCHAR dwFamilyName;
        if (nullptr == familyName) {
            HR_GENERAL(getDefaultFontFamilyName(&dwFamilyName), nullptr, SkRemotableFontIdentitySet::NewEmpty());
        } else {
            HR_GENERAL(sk_cstring_to_wchar(familyName, &dwFamilyName), nullptr, SkRemotableFontIdentitySet::NewEmpty());
        }

        UINT32 index;
        BOOL exists;
        HR_GENERAL(fFontCollection->FindFamilyName(dwFamilyName.get(), &index, &exists),
            "Failed while finding family by name.", SkRemotableFontIdentitySet::NewEmpty());
        if (!exists) {
            return SkRemotableFontIdentitySet::NewEmpty();
        }

        return this->getIndex(index);
    }

    virtual SkFontIdentity matchNameStyle(const char familyName[], const SkFontStyle &style) const override
    {
        SkFontIdentity identity = { SkFontIdentity::kInvalidDataId };

        SkSMallocWCHAR dwFamilyName;
        if (nullptr == familyName) {
            HR_GENERAL(getDefaultFontFamilyName(&dwFamilyName), nullptr, identity);
        } else {
            HR_GENERAL(sk_cstring_to_wchar(familyName, &dwFamilyName), nullptr, identity);
        }

        UINT32 index;
        BOOL exists;
        HR_GENERAL(fFontCollection->FindFamilyName(dwFamilyName.get(), &index, &exists),
            "Failed while finding family by name.", identity);
        if (!exists) {
            return identity;
        }

        return this->matchIndexStyle(index, style);
    }

    class FontFallbackRenderer : public IDWriteTextRenderer {
    public:
        FontFallbackRenderer(const SkRemotableFontMgr_DirectWrite *outer, UINT32 character)
            : fRefCount(1), fOuter(SkSafeRef(outer)), fCharacter(character)
        {
            fIdentity.fDataId = SkFontIdentity::kInvalidDataId;
        }

        virtual ~FontFallbackRenderer() {}

        // IDWriteTextRenderer methods
        SK_STDMETHODIMP DrawGlyphRun(void *clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY,
            DWRITE_MEASURING_MODE measuringMode, DWRITE_GLYPH_RUN const * glyphRun,
            DWRITE_GLYPH_RUN_DESCRIPTION const * glyphRunDescription, IUnknown *clientDrawingEffect) override
        {
            SkTScopedComPtr<IDWriteFont> font;
            HRM(fOuter->fFontCollection->GetFontFromFontFace(glyphRun->fontFace, &font),
                "Could not get font from font face.");

            // It is possible that the font passed does not actually have the requested character,
            // due to no font being found and getting the fallback font.
            // Check that the font actually contains the requested character.
            BOOL exists;
            HRM(font->HasCharacter(fCharacter, &exists), "Could not find character.");

            if (exists) {
                HR(fOuter->FontToIdentity(font.get(), &fIdentity));
            }

            return S_OK;
        }

        SK_STDMETHODIMP DrawUnderline(void *clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY,
            DWRITE_UNDERLINE const * underline, IUnknown *clientDrawingEffect) override
        {
            return E_NOTIMPL;
        }

        SK_STDMETHODIMP DrawStrikethrough(void *clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY,
            DWRITE_STRIKETHROUGH const * strikethrough, IUnknown *clientDrawingEffect) override
        {
            return E_NOTIMPL;
        }

        SK_STDMETHODIMP DrawInlineObject(void *clientDrawingContext, FLOAT originX, FLOAT originY,
            IDWriteInlineObject *inlineObject, BOOL isSideways, BOOL isRightToLeft,
            IUnknown *clientDrawingEffect) override
        {
            return E_NOTIMPL;
        }

        // IDWritePixelSnapping methods
        SK_STDMETHODIMP IsPixelSnappingDisabled(void *clientDrawingContext, BOOL *isDisabled) override
        {
            *isDisabled = FALSE;
            return S_OK;
        }

        SK_STDMETHODIMP GetCurrentTransform(void *clientDrawingContext, DWRITE_MATRIX *transform) override
        {
            const DWRITE_MATRIX ident = { 1.0, 0.0, 0.0, 1.0, 0.0, 0.0 };
            *transform = ident;
            return S_OK;
        }

        SK_STDMETHODIMP GetPixelsPerDip(void *clientDrawingContext, FLOAT *pixelsPerDip) override
        {
            *pixelsPerDip = 1.0f;
            return S_OK;
        }

        // IUnknown methods
        SK_STDMETHODIMP_(ULONG)AddRef() override
        {
            return InterlockedIncrement(&fRefCount);
        }

        SK_STDMETHODIMP_(ULONG)Release() override
        {
            ULONG newCount = InterlockedDecrement(&fRefCount);
            if (0 == newCount) {
                delete this;
            }
            return newCount;
        }

        SK_STDMETHODIMP QueryInterface(IID const & riid, void **ppvObject) override
        {
            if (__uuidof(IUnknown) == riid || __uuidof(IDWritePixelSnapping) == riid ||
                __uuidof(IDWriteTextRenderer) == riid) {
                *ppvObject = this;
                this->AddRef();
                return S_OK;
            }
            *ppvObject = nullptr;
            return E_FAIL;
        }

        const SkFontIdentity FallbackIdentity()
        {
            return fIdentity;
        }

    protected:
        ULONG fRefCount;
        sk_sp<const SkRemotableFontMgr_DirectWrite> fOuter;
        UINT32 fCharacter;
        SkFontIdentity fIdentity;
    };

    virtual SkFontIdentity matchNameStyleCharacter(const char familyName[], const SkFontStyle &pattern,
        const char *bcp47[], int bcp47Count, SkUnichar character) const override
    {
        SkFontIdentity identity = { SkFontIdentity::kInvalidDataId };

        IDWriteFactory *dwFactory = sk_get_dwrite_factory();
        if (nullptr == dwFactory) {
            return identity;
        }

        // TODO: use IDWriteFactory2::GetSystemFontFallback when available.

        const DWriteStyle dwStyle(pattern);

        SkSMallocWCHAR dwFamilyName;
        if (nullptr == familyName) {
            HR_GENERAL(getDefaultFontFamilyName(&dwFamilyName), nullptr, identity);
        } else {
            HR_GENERAL(sk_cstring_to_wchar(familyName, &dwFamilyName), nullptr, identity);
        }

        const SkSMallocWCHAR *dwBcp47;
        SkSMallocWCHAR dwBcp47Local;
        if (bcp47Count < 1) {
            dwBcp47 = &fLocaleName;
        } else {
            // TODO: support fallback stack.
            HR_GENERAL(sk_cstring_to_wchar(bcp47[bcp47Count - 1], &dwBcp47Local), nullptr, identity);
            dwBcp47 = &dwBcp47Local;
        }

        SkTScopedComPtr<IDWriteTextFormat> fallbackFormat;
        HR_GENERAL(dwFactory->CreateTextFormat(dwFamilyName, fFontCollection.get(), dwStyle.fWeight, dwStyle.fSlant,
            dwStyle.fWidth, 72.0f, *dwBcp47, &fallbackFormat),
            "Could not create text format.", identity);

        WCHAR str[16];
        UINT32 strLen = static_cast<UINT32>(SkUTF::ToUTF16(character, reinterpret_cast<uint16_t *>(str)));
        SkTScopedComPtr<IDWriteTextLayout> fallbackLayout;
        HR_GENERAL(dwFactory->CreateTextLayout(str, strLen, fallbackFormat.get(), 200.0f, 200.0f, &fallbackLayout),
            "Could not create text layout.", identity);

        SkTScopedComPtr<FontFallbackRenderer> fontFallbackRenderer(new FontFallbackRenderer(this, character));

        HR_GENERAL(fallbackLayout->Draw(nullptr, fontFallbackRenderer.get(), 50.0f, 50.0f),
            "Could not draw layout with renderer.", identity);

        return fontFallbackRenderer->FallbackIdentity();
    }

    SkStreamAsset *getData(int dataId) const override
    {
        SkAutoMutexExclusive ama(fDataIdCacheMutex);
        if (dataId >= fDataIdCache.size()) {
            return nullptr;
        }
        const DataId &id = fDataIdCache[dataId];

        SkTScopedComPtr<IDWriteFontFileLoader> loader;
        HRNM(id.fLoader->QueryInterface(&loader), "QuerryInterface IDWriteFontFileLoader failed");

        SkTScopedComPtr<IDWriteFontFileStream> fontFileStream;
        HRNM(loader->CreateStreamFromKey(id.fKey, id.fKeySize, &fontFileStream), "Could not create font file stream.");

        return new SkDWriteFontFileStream(fontFileStream.get());
    }

private:
    SkTScopedComPtr<IDWriteFontCollection> fFontCollection;
    SkSMallocWCHAR fLocaleName;

    using INHERITED = SkRemotableFontMgr;
};

SkRemotableFontMgr *SkRemotableFontMgr_New_DirectWrite()
{
    IDWriteFactory *factory = sk_get_dwrite_factory();
    if (nullptr == factory) {
        return nullptr;
    }

    SkTScopedComPtr<IDWriteFontCollection> sysFontCollection;
    HRNM(factory->GetSystemFontCollection(&sysFontCollection, FALSE), "Could not get system font collection.");

    WCHAR localeNameStorage[LOCALE_NAME_MAX_LENGTH];
    WCHAR *localeName = nullptr;
    int localeNameLen = 0;

    // Dynamically load GetUserDefaultLocaleName function, as it is not available on XP.
    SkGetUserDefaultLocaleNameProc getUserDefaultLocaleNameProc = nullptr;
    HRESULT hr = SkGetGetUserDefaultLocaleNameProc(&getUserDefaultLocaleNameProc);
    if (nullptr == getUserDefaultLocaleNameProc) {
        SK_TRACEHR(hr, "Could not get GetUserDefaultLocaleName.");
    } else {
        localeNameLen = getUserDefaultLocaleNameProc(localeNameStorage, LOCALE_NAME_MAX_LENGTH);
        if (localeNameLen) {
            localeName = localeNameStorage;
        };
    }

    return new SkRemotableFontMgr_DirectWrite(sysFontCollection.get(), localeName, localeNameLen);
}
#endif // defined(SK_BUILD_FOR_WIN)
